#ifndef __H_BUFFERPOOL__
#define __H_BUFFERPOOL__

#include "link.h"
#include "synchronizer.h"

#include <pshpack8.h>   // set structure packing to 8

namespace bl{

class CircleBufferPool
{
public:
	CircleBufferPool(int itemSize, int itemDefaultNo = 20) : buffer_written_(false, false)
	{
		item_size_ = itemSize;

		mem_block_ = (PDLINK)new char[sizeof(DLINK)+itemSize];
		Dlink_Initialize(mem_block_);

		for(int i=0; i<itemDefaultNo-1; i++)
		{
			PDLINK newNode = (PDLINK)new char[sizeof(DLINK)+itemSize];
			Dlink_InsertNext(mem_block_, newNode);
		}

		write_pos_ = read_pos_ = mem_block_;

#ifdef DEBUG
		buffers_written_ = buffers_read_ = buffers_left_ = 0;
		//buffer_point_cs_.is_trace_ = true;
#endif
	}

	~CircleBufferPool()
	{
		while( !Dlink_IsEmpty(mem_block_) )
		{
			char* buf = (char*)mem_block_->next;
			PDLINK next = mem_block_->next;
			Dlink_Remove(next);
			delete[] buf;
		}

		char* head = (char*)mem_block_;
		delete[] head;
	}

	void Reset()
	{
		bl::AutoLockCS lock(buffer_point_cs_);
		{
			write_pos_ = read_pos_ = mem_block_;
		}

		buffer_written_.Set();
	}

	bool IsEmpty()
	{
		bl::AutoLockCS lock(buffer_point_cs_);
		if(read_pos_ == write_pos_)
		{
			return true;
		}

		return false;
	}

	void WriteBuffer(const unsigned char* content)
	{
		buffer_point_cs_.Lock();
		if( write_pos_->prev == read_pos_ )
		{
			buffer_point_cs_.Unlock();

			// allocate
			for(int i=0; i<20; i++)
			{
				PDLINK newNode = (PDLINK)new char[sizeof(DLINK)+item_size_];
				Dlink_InsertBefore(write_pos_, newNode);
			}
		}
		else
		{
			buffer_point_cs_.Unlock();
		}

		char* buff = (char*)write_pos_ + sizeof(DLINK);
		memcpy(buff, content, item_size_);

		buffer_point_cs_.Lock();
		write_pos_ = write_pos_->prev;
		buffer_point_cs_.Unlock();

#ifdef DEBUG
		buffers_written_++;
		InterlockedIncrement(&buffers_left_);
#endif

		buffer_written_.Set();
	}

	void ReadBuffer(unsigned char* content)
	{
		if(read_pos_ == write_pos_)
		{
			// wait
			buffer_written_.Wait();
		}

		char* buff = (char*)read_pos_ + sizeof(DLINK);
		memcpy(content, buff, item_size_);
		read_pos_ = read_pos_->prev;

#ifdef DEBUG
		buffers_read_++;
		InterlockedDecrement(&buffers_left_);
#endif

		buffer_written_.Reset();
	}


	unsigned char* GetReadBuffer(unsigned long timeOut = 0)
	{
		if(IsEmpty())
		{
			if(timeOut == 0)
				return NULL;
			// wait
			buffer_written_.Wait(timeOut);
		}

		if(IsEmpty())
			return NULL;

		buffer_written_.Reset();

		unsigned char* buff = (unsigned char*)read_pos_ + sizeof(DLINK);
		return buff;
	}

	void ReleaseReadBuffer()
	{
		bl::AutoLockCS lock(buffer_point_cs_);
		read_pos_ = read_pos_->prev;

#ifdef DEBUG
		buffers_read_++;
		InterlockedDecrement(&buffers_left_);
#endif
	}

private:
	PDLINK mem_block_;
	PDLINK write_pos_;
	PDLINK read_pos_;

	bl::CritSec buffer_point_cs_;
	bl::Event buffer_written_;

	int item_size_;

#ifdef DEBUG
	LONG buffers_written_;
	LONG buffers_read_;
	LONG buffers_left_;
#endif
};

}

#include <poppack.h>    // pop structure packing back to previous state

#endif